(() => {
  const CONFIG = {
    rows: 8,
    cols: 12,
    tileTypes: 12,
    countdownSeconds: 180,
    scorePerMatch: 10,
    scorePenaltyShuffle: 5,
  };

  const SYMBOLS = [
    "🍎",
    "🍌",
    "🍇",
    "🍓",
    "🍑",
    "🥝",
    "🍉",
    "🍍",
    "🍒",
    "🥥",
    "🥕",
    "🌽",
    "🍅",
    "🍋",
    "🍐",
  ];

  const state = {
    board: [],
    selected: [],
    steps: 0,
    score: 0,
    remainingSeconds: CONFIG.countdownSeconds,
    timerId: null,
    isRunning: false,
    hintTiles: [],
  };

  const dom = {};

  document.addEventListener("DOMContentLoaded", init);

  function init() {
    cacheDom();
    bindEvents();
    startGame();
    window.addEventListener("resize", throttle(resizeCanvas, 150));
  }

  function cacheDom() {
    dom.board = document.getElementById("board");
    dom.template = document.getElementById("tile-template");
    dom.timer = document.getElementById("timer");
    dom.score = document.getElementById("score");
    dom.steps = document.getElementById("steps");
    dom.btnRestart = document.getElementById("btn-restart");
    dom.btnShuffle = document.getElementById("btn-shuffle");
    dom.btnHint = document.getElementById("btn-hint");
    dom.canvas = document.getElementById("path-canvas");
    dom.canvasCtx = dom.canvas.getContext("2d");
    dom.gameMain = document.querySelector(".game-main");
  }

  function bindEvents() {
    dom.btnRestart.addEventListener("click", startGame);
    dom.btnShuffle.addEventListener("click", onShuffle);
    dom.btnHint.addEventListener("click", onHint);
  }

  function startGame() {
    resetState();
    if (!generateBoard()) {
      showModal("初始化失败", "请调整棋盘或图案数量", "重试", startGame);
      return;
    }
    renderBoard();
    resizeCanvas();
    startTimer();
    hideModal();
  }

  function resetState() {
    state.board = [];
    state.selected = [];
    state.steps = 0;
    state.score = 0;
    state.remainingSeconds = CONFIG.countdownSeconds;
    state.isRunning = true;
    state.hintTiles = [];
    stopTimer();
    updateHud();
    clearCanvas();
  }

  function generateBoard() {
    const { rows, cols, tileTypes } = CONFIG;
    const totalCells = rows * cols;
    if (totalCells % 2 !== 0) {
      console.warn("棋盘格子数必须为偶数");
      return false;
    }
    const usableSymbols = SYMBOLS.slice(0, Math.min(tileTypes, SYMBOLS.length));
    const pairsNeeded = totalCells / 2;
    const pool = [];
    for (let i = 0; i < pairsNeeded; i += 1) {
      const symbol = usableSymbols[i % usableSymbols.length];
      pool.push(symbol, symbol);
    }
    shuffleArray(pool);
    const board = [];
    for (let r = 0; r < rows; r += 1) {
      board[r] = [];
      for (let c = 0; c < cols; c += 1) {
        board[r][c] = pool[r * cols + c];
      }
    }
    state.board = board;
    return true;
  }

  function renderBoard() {
    const { board } = state;
    const { rows, cols } = CONFIG;
    dom.board.innerHTML = "";
    dom.board.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
    for (let r = 0; r < rows; r += 1) {
      for (let c = 0; c < cols; c += 1) {
        const tileValue = board[r][c];
        const tile = dom.template.content.firstElementChild.cloneNode(true);
        tile.dataset.row = String(r);
        tile.dataset.col = String(c);
        tile.dataset.value = tileValue ?? "";
        tile.addEventListener("click", onTileClick);
        if (tileValue == null) {
          tile.disabled = true;
        } else {
          tile.textContent = tileValue;
        }
        if (state.selected.some((pos) => pos.row === r && pos.col === c)) {
          tile.classList.add("selected");
        }
        if (state.hintTiles.some((pos) => pos.row === r && pos.col === c)) {
          tile.classList.add("hint");
        }
        dom.board.appendChild(tile);
      }
    }
  }

  function onTileClick(event) {
    if (!state.isRunning) return;
    const tile = event.currentTarget;
    const row = Number(tile.dataset.row);
    const col = Number(tile.dataset.col);
    const value = tile.dataset.value;
    if (!value) return;
    if (state.selected.length === 1) {
      const [first] = state.selected;
      if (first.row === row && first.col === col) {
        state.selected = [];
        renderBoard();
        clearCanvas();
        return;
      }
      tryMatch(first, { row, col, value });
    } else {
      state.selected = [{ row, col, value }];
      renderBoard();
      clearCanvas();
    }
  }

  function tryMatch(first, second) {
    if (first.value !== second.value) {
      state.selected = [second];
      renderBoard();
      clearCanvas();
      return;
    }
    const path = findPath(first, second);
    if (!path) {
      state.selected = [second];
      renderBoard();
      clearCanvas();
      return;
    }
    drawPath(path);
    setTimeout(() => {
      removeTiles(first, second);
      clearCanvas();
      checkGameProgress();
    }, 220);
  }

  function removeTiles(first, second) {
    const { board } = state;
    board[first.row][first.col] = null;
    board[second.row][second.col] = null;
    state.selected = [];
    state.steps += 1;
    state.score += CONFIG.scorePerMatch;
    updateHud();
    renderBoard();
  }

  function findPath(first, second) {
    const { board } = state;
    const rows = board.length;
    const cols = board[0].length;
    const expandedRows = rows + 2;
    const expandedCols = cols + 2;
    const start = { row: first.row + 1, col: first.col + 1 };
    const target = { row: second.row + 1, col: second.col + 1 };
    const expanded = Array.from({ length: expandedRows }, () =>
      Array(expandedCols).fill(null)
    );
    for (let r = 0; r < rows; r += 1) {
      for (let c = 0; c < cols; c += 1) {
        expanded[r + 1][c + 1] = board[r][c];
      }
    }
    const visited = Array.from({ length: expandedRows }, () =>
      Array.from({ length: expandedCols }, () => Array(4).fill(Infinity))
    );
    const queue = [];
    queue.push({
      row: start.row,
      col: start.col,
      dir: -1,
      turns: 0,
      path: [{ row: start.row, col: start.col }],
    });
    const directions = [
      { dr: -1, dc: 0 },
      { dr: 0, dc: 1 },
      { dr: 1, dc: 0 },
      { dr: 0, dc: -1 },
    ];
    while (queue.length > 0) {
      const current = queue.shift();
      for (let i = 0; i < directions.length; i += 1) {
        const { dr, dc } = directions[i];
        let nextRow = current.row + dr;
        let nextCol = current.col + dc;
        let turns = current.turns;
        if (current.dir !== -1 && current.dir !== i) {
          turns += 1;
        }
        if (turns > 2) continue;
        while (
          nextRow >= 0 &&
          nextRow < expandedRows &&
          nextCol >= 0 &&
          nextCol < expandedCols
        ) {
          const cellValue = expanded[nextRow][nextCol];
          const isTarget = nextRow === target.row && nextCol === target.col;
          if (!isTarget && cellValue !== null) {
            break;
          }
          if (visited[nextRow][nextCol][i] <= turns) {
            if (!isTarget) {
              nextRow += dr;
              nextCol += dc;
              continue;
            }
          } else {
            visited[nextRow][nextCol][i] = turns;
          }
          const newPath = current.path.concat({ row: nextRow, col: nextCol });
          if (isTarget) {
            return newPath;
          }
          queue.push({
            row: nextRow,
            col: nextCol,
            dir: i,
            turns,
            path: newPath,
          });
          nextRow += dr;
          nextCol += dc;
        }
      }
    }
    return null;
  }

  function checkGameProgress() {
    if (isBoardCleared()) {
      state.isRunning = false;
      stopTimer();
      showModal(
        "恭喜通关！",
        `用时 ${formatTime(CONFIG.countdownSeconds - state.remainingSeconds)}，得分 ${state.score}`,
        "再来一局",
        startGame
      );
      return;
    }
    if (!hasRemainingMoves()) {
      showModal("无可连图案", "系统已自动为你洗牌一次", "继续游戏", () => {
        shuffleBoard();
        hideModal();
      });
    }
  }

  function isBoardCleared() {
    return state.board.every((row) => row.every((cell) => cell == null));
  }

  function hasRemainingMoves() {
    const { board } = state;
    const rows = board.length;
    const cols = board[0].length;
    const map = new Map();
    for (let r = 0; r < rows; r += 1) {
      for (let c = 0; c < cols; c += 1) {
        const value = board[r][c];
        if (value == null) continue;
        if (!map.has(value)) map.set(value, []);
        map.get(value).push({ row: r, col: c, value });
      }
    }
    for (const positions of map.values()) {
      if (positions.length < 2) continue;
      for (let i = 0; i < positions.length - 1; i += 1) {
        for (let j = i + 1; j < positions.length; j += 1) {
          if (findPath(positions[i], positions[j])) {
            return true;
          }
        }
      }
    }
    return false;
  }

  function onShuffle() {
    if (!state.isRunning) return;
    shuffleBoard();
    state.steps += 1;
    state.score = Math.max(0, state.score - CONFIG.scorePenaltyShuffle);
    updateHud();
    renderBoard();
  }

  function shuffleBoard() {
    const freshTiles = [];
    const { board } = state;
    for (let r = 0; r < board.length; r += 1) {
      for (let c = 0; c < board[r].length; c += 1) {
        if (board[r][c] != null) freshTiles.push(board[r][c]);
      }
    }
    shuffleArray(freshTiles);
    let pointer = 0;
    for (let r = 0; r < board.length; r += 1) {
      for (let c = 0; c < board[r].length; c += 1) {
        if (board[r][c] != null) {
          board[r][c] = freshTiles[pointer];
          pointer += 1;
        }
      }
    }
    state.selected = [];
    state.hintTiles = [];
    renderBoard();
    clearCanvas();
  }

  function onHint() {
    if (!state.isRunning) return;
    const hint = findHint();
    if (!hint) {
      flashHintMessage("当前无可连图案，建议洗牌");
      return;
    }
    state.hintTiles = hint;
    renderBoard();
    setTimeout(() => {
      state.hintTiles = [];
      renderBoard();
    }, 1200);
  }

  function findHint() {
    const { board } = state;
    const rows = board.length;
    const cols = board[0].length;
    const map = new Map();
    for (let r = 0; r < rows; r += 1) {
      for (let c = 0; c < cols; c += 1) {
        const value = board[r][c];
        if (value == null) continue;
        if (!map.has(value)) map.set(value, []);
        map.get(value).push({ row: r, col: c, value });
      }
    }
    for (const positions of map.values()) {
      for (let i = 0; i < positions.length - 1; i += 1) {
        for (let j = i + 1; j < positions.length; j += 1) {
          if (findPath(positions[i], positions[j])) {
            return [positions[i], positions[j]];
          }
        }
      }
    }
    return null;
  }

  function flashHintMessage(message) {
    const bubble = document.createElement("div");
    bubble.className = "message-modal";
    bubble.innerHTML = `<h2>${message}</h2><button type="button">关闭</button>`;
    const close = () => {
      bubble.remove();
    };
    bubble.querySelector("button").addEventListener("click", () => {
      close();
    });
    dom.gameMain.appendChild(bubble);
    setTimeout(close, 1500);
  }

  function startTimer() {
    stopTimer();
    updateTimerDisplay();
    state.timerId = window.setInterval(() => {
      if (!state.isRunning) return;
      state.remainingSeconds -= 1;
      if (state.remainingSeconds <= 0) {
        state.remainingSeconds = 0;
        updateTimerDisplay();
        handleGameOver();
        return;
      }
      updateTimerDisplay();
    }, 1000);
  }

  function stopTimer() {
    if (state.timerId != null) {
      clearInterval(state.timerId);
      state.timerId = null;
    }
  }

  function handleGameOver() {
    state.isRunning = false;
    stopTimer();
    showModal(
      "时间结束",
      `很遗憾未完成，本局得分 ${state.score}`,
      "再试一次",
      startGame
    );
  }

  function updateHud() {
    dom.score.textContent = String(state.score);
    dom.steps.textContent = String(state.steps);
  }

  function updateTimerDisplay() {
    dom.timer.textContent = formatTime(state.remainingSeconds);
  }

  function formatTime(seconds) {
    const m = Math.floor(seconds / 60)
      .toString()
      .padStart(2, "0");
    const s = (seconds % 60).toString().padStart(2, "0");
    return `${m}:${s}`;
  }

  function drawPath(path) {
    resizeCanvas();
    clearCanvas();
    if (!path || path.length < 2) return;
    const boardStyle = getComputedStyle(dom.board);
    const gap = parseFloat(boardStyle.gap) || 0;
    const paddingX = parseFloat(boardStyle.paddingLeft) || 0;
    const paddingY = parseFloat(boardStyle.paddingTop) || 0;
    const tile = dom.board.querySelector(".tile");
    if (!tile) return;
    const tileWidth = tile.offsetWidth;
    const tileHeight = tile.offsetHeight;
    const totalCols = CONFIG.cols;
    const totalRows = CONFIG.rows;

    const convert = (expRow, expCol) => {
      const calcCoord = (index, size, gapSize, padding, maxIndex) => {
        if (index === 0) {
          return padding - gapSize / 2 - size / 2;
        }
        if (index === maxIndex + 1) {
          return (
            padding +
            (size + gapSize) * maxIndex -
            gapSize / 2 +
            size / 2
          );
        }
        return (
          padding +
          (size + gapSize) * (index - 1) +
          size / 2
        );
      };
      return {
        x: calcCoord(expCol, tileWidth, gap, paddingX, totalCols),
        y: calcCoord(expRow, tileHeight, gap, paddingY, totalRows),
      };
    };

    const ctx = dom.canvasCtx;
    ctx.save();
    ctx.strokeStyle = "rgba(90, 96, 255, 0.8)";
    ctx.lineWidth = Math.min(tileWidth, tileHeight) * 0.2;
    ctx.lineCap = "round";
    ctx.lineJoin = "round";
    ctx.beginPath();
    path.forEach((point, index) => {
      const coord = convert(point.row, point.col);
      if (index === 0) {
        ctx.moveTo(coord.x, coord.y);
      } else {
        ctx.lineTo(coord.x, coord.y);
      }
    });
    ctx.stroke();
    ctx.restore();
  }

  function clearCanvas() {
    dom.canvasCtx.clearRect(0, 0, dom.canvas.width, dom.canvas.height);
  }

  function resizeCanvas() {
    const rect = dom.board.getBoundingClientRect();
    dom.canvas.width = rect.width;
    dom.canvas.height = rect.height;
  }

  function showModal(title, message, buttonText, onConfirm) {
    hideModal();
    const modal = document.createElement("div");
    modal.className = "message-modal";
    modal.innerHTML = `
      <div>
        <h2>${title}</h2>
        <p>${message}</p>
        <button type="button">${buttonText}</button>
      </div>
    `;
    modal.querySelector("button").addEventListener("click", () => {
      hideModal();
      if (typeof onConfirm === "function") {
        onConfirm();
      }
    });
    dom.gameMain.appendChild(modal);
  }

  function hideModal() {
    const existing = dom.gameMain.querySelector(".message-modal");
    if (existing) existing.remove();
  }

  function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i -= 1) {
      const j = Math.floor(Math.random() * (i + 1));
      [array[i], array[j]] = [array[j], array[i]];
    }
  }

  function throttle(fn, wait) {
    let lastTime = 0;
    let timer = null;
    return function throttled(...args) {
      const now = Date.now();
      const remaining = wait - (now - lastTime);
      if (remaining <= 0) {
        if (timer) {
          clearTimeout(timer);
          timer = null;
        }
        lastTime = now;
        fn.apply(this, args);
      } else if (!timer) {
        timer = setTimeout(() => {
          lastTime = Date.now();
          timer = null;
          fn.apply(this, args);
        }, remaining);
      }
    };
  }
})();

